package malha;
import java.util.Set;
import java.util.Vector;
import java.util.HashMap;

import controle.CControle;

/**
 * Uma chave que interliga dois conjuntos de trechos
 *
 */
public class CChave extends CElementoTrilho {

	private Vector<String> trechosA = new Vector<String>();
	private Vector<String> trechosB = new Vector<String>();
	private int tempoMudanca = -1;
	private boolean emMudanca = false;
	private int tempoConclusao = -1;
	private HashMap<CComposicao, Integer> trensACaminho = new HashMap<CComposicao, Integer>();
	private CComposicao composicaoPassando = null;
	
	static final int TEMPO_SEGURANCA = 10;
	
	/**
	 * Construtor com ID
	 */
	public CChave(String id) {
		super(id);
	}
	
	/**
	 * Os trechos ligados a extremidade A desta chave
	 * 
	 * @return Um conjunto contendo os CTrechos ligados a extremidade A desta chave
	 */
	public Vector<String> getTrechosA() {
		return trechosA;
	}
	
	/**
	 * Seta os trechos no lado A da chave
	 * @param trechosA
	 */
	public void setTrechosA(Vector<String> trechosA) {
		this.trechosA = trechosA;
	}

	/**
	 * Os trechos ligados a extremidade B desta chave
	 * @return Um conjunto contendo os CTrechos ligados a extremidade B desta chave
	 */
	public Vector<String> getTrechosB() {
		return trechosB;
	}

	/**
	 * Seta os trechos no lado B da chave
	 * @param trechosB
	 */
	public void setTrechosB(Vector<String> trechosB) {
		this.trechosB = trechosB;
	}
	
	/**
	 * O tempo que esta chave demora para mudar seu estado
	 * 
	 * @return Um inteiro representando o tempo necessario, em segundos,
	 * para esta chave realizar a total mudanca entre estados
	 * finalizar a mudanca de estado em segundos
	 */
	public int getTempoMudanca() {
		return tempoMudanca;
	}

	/**
	 * Seta o tempo necessario para mudar o 
	 * estado da chave
	 * @param tempoMudanca
	 */
	public void setTempoMudanca(int tempoMudanca) {
		this.tempoMudanca = tempoMudanca;
	}
	
	/**
	 * Informa se a chave esta ou nao em mudanca de estado
	 * 
	 * @return True se a chave estiver mudando de estado, False se 
	 * estiver em repouso
	 */
	public boolean isEmMudanca() {
		return emMudanca;
	}

	/**
	 * Define que a chave esta mudando de estado
	 * @param emMudanca
	 */
	public void setEmMudanca(boolean emMudanca) {
		this.emMudanca = emMudanca;
	}
	
	/**
	 * Informa o tempo restante para a conclusao da mudanca
	 * de estado da chave
	 * 
	 * @return Um inteiro representando a quantidade de segundos 
	 * restantes para a chave terminar de mudar seu estado
	 */
	public int getTempoConclusao() {
		return tempoConclusao;
	}

	/**
	 * Atualiza o tempo de conclusao para
	 * Mudanca de chave
	 * @param tempoConclusao
	 */
	public void setTempoConclusao(int tempoConclusao) {
		this.tempoConclusao = tempoConclusao;
	}
	
	/** A composicao passando pela chave atualmente
	 * @return A composicao que esta passando pela chave ou null
	 * caso a chave esteja vazia
	 */
	public CComposicao getComposicaoPassando() {
		return composicaoPassando;
	}
	
	/** Seta a composicao passando pela chave atualmente
	 */
	public CComposicao setComposicaoPassando(CComposicao comp) {
		return this.composicaoPassando = comp;
	}
	
	/**
	 * Um mapa representando os proximos trens de cada trecho
	 * que estao a caminho desta chave associados ao tempo em que
	 * o farao
	 * 
	 * @return Um Set que possui as proximos CComposicoes a caminho
	 */
	public HashMap<CComposicao,Integer> getTrensACaminho() {
		return trensACaminho;
	}
	
	/**
	 * Seta o trecho no lado A da chave
	 * 
	 * @param novoTrecho O trecho a ser conectado ao lado A desta chave
	 * @param inicializandoMalha Se for verdadeiro, forca a mudanca do atributo para fins 
	 * de inicializacao
	 * 
	 * @pre O novo trecho deve pertencer ao conjunto de 
	 * trechos do lado A da chave (Pre 1)
	 * @pre Nao deve haver composicao passando pela chave (Pre 2)
	 * @post O trecho ligado a extremidade A e o novo trecho (Pos 1)
	 */
	@Override
	public void setElementoAtualA(CElementoTrilho novoTrecho, boolean inicializandoMalha)
	{
		/*Pre 1*/
		assert(inicializandoMalha || trechosA.contains(novoTrecho.getId()));
		/*Pre 2*/
		assert(inicializandoMalha || composicaoPassando == null);
		
		/*<IMPLEMENTACAO>*/
		this.elementoAtualA = novoTrecho;

		/*</IMPLEMENTACAO>*/

		/*Pos 1*/
		assert(inicializandoMalha || getElementoAtualA() == novoTrecho);
	}
	
	/**
	 * Seta o trecho no lado B da chave
	 * 
	 * @param novoTrecho O trecho a ser conectado ao lado B desta chave
	 * @param inicializandoMalha Se for verdadeiro, forca a mudanca do atributo para fins 
	 * de inicializacao
	 *
	 * Seta o trecho no lado B da chave
	 * @pre O novo trecho deve pertencer ao conjunto de 
	 * trechos do lado B da chave (Pre 1)
	 * @pre Nao deve haver composicao passando pela chave (Pre 2)
	 * @post O trecho ligado a extremidade B eh o novo trecho (Pos 1)
	 * @post A chave nao esta em mudanca de estado (Pos 2)
	 */
	@Override
	public void setElementoAtualB(CElementoTrilho novoTrecho, boolean inicializandoMalha)
	{
		/*Pre 1*/
		assert(inicializandoMalha || trechosB.contains(novoTrecho.getId()));
		/*Pre 2*/
		assert(inicializandoMalha || composicaoPassando == null);
		
		/*<IMPLEMENTACAO>*/
		this.elementoAtualB = novoTrecho;
		/*</IMPLEMENTACAO>*/
		
		/*Pos 1*/
		assert(getElementoAtualB() == novoTrecho);
		
	}
	
	/**
	 * Modifica a chave de forma a ligar dois trechos passados como parametro
	 * 
	 * @param trechoA CTrecho que deve ser ligado a extremidade A da chave
	 * @param trechoB CTrecho que deve ser ligado a extremidade B da chave
	 * 
	 * @pre O trecho representado por idTrechoA deve pertencer ao conjunto de CTrechos<code>trechosA</code> (Pre 1)
	 * @pre O trecho representado por idTrechoB deve pertencer ao conjunto de CTrechos<code>trechosB</code> (Pre 2)
	 * @pre A chave n�o pode estar mudando de estado (Pre 3)
	 * @pre Nao pode haver uma CComposicao transitando pela chave (Pre 4)
	 * @pre O tempo para a mudan�a da chave n�o pode ser maior que dT + secTime, onde dT � o tempo 
	 * no qual a pr�xima composi��o atingir� a chave e secTime um tempo de seguran�a (Pre 5)
	 * 
	 * @inv O conjunto de composi??es a caminho desta chave n?o se altera
	 * 
	 * @post A chave se encontra no estado de mudanca e o tempo para finaliza-la diminui a cada atualizacao da malha (Post 1)
	 * @post Os elementos atual A e B setados de acordo com os parametros
	 */
	public void mudarChave(CTrecho trechoA, CTrecho trechoB) {
		
		/*Pre 1*/
		assert(trechosA.contains(trechoA.getId())); 
		/*Pre 2*/
		assert(trechosB.contains(trechoB.getId()));
		/*Pre 3*/
		assert(!emMudanca);
		/*Pre 4*/
		assert(composicaoPassando == null);
		/*Pre 5*/
		if(proximaComposicaoACaminho() != null)
		{
			assert(trensACaminho.get(proximaComposicaoACaminho()) >= tempoConclusao);
		}
		
		/*<IMPLEMENTACAO>*/
		emMudanca = true;
		setElementoAtualA(trechoA, false);
		setElementoAtualB(trechoB, false);
		tempoConclusao = tempoMudanca;
		/*</IMPLEMENTACAO>*/
		
		/*Post 1*/
		assert(emMudanca);
		/*Post 2*/
		assert(getElementoAtualA() == trechoA && getElementoAtualB() == trechoB);
	}
	
	
	/**
	 * Atualiza a mudanca de estado da chave
	 * 
	 * @pre A chave deve estar em processo de mudanca de estado (Pre 1)
	 * 
	 * @post O tempo de mudanca e decrementado (Post 1)
	 * @post Se o tempo de mudanca atingir zero a chave muda para estado de repouso (Post 2)
	 * e os trechos estao corretamente setados em cada extremidade da chave
	 * 
	 * @inv A quantidade de composicoes nesta chave continua igual a zero (Inv 1)
	 */
	public void atualizarEstado(){
		int tempoInicial = tempoConclusao;

		/*Inv 1*/
		assert(composicaoPassando == null);
		
		/*Pre 1*/
		assert(isEmMudanca());
		
		/*<IMPLEMENTACAO>*/
		tempoConclusao--;
		
		if(tempoConclusao == 0) {
			setEmMudanca(false);
		}
		/*</IMPLEMENTACAO>*/
		
		/*Post 1*/
		assert(tempoInicial == tempoConclusao + 1);
		/*Post 2*/
		assert((tempoConclusao == 0) || isEmMudanca());
		
		/*Inv 1*/
		assert(composicaoPassando == null);
	}
	
	
	/**
	 * Adiciona uma CComposicao no conjunto de composicoes que se
	 * dirigem para esta chave.
	 * 
	 * @param composicao Uma CComposicao que se dirige a esta chave
	 * 
	 * @pre <code>composicao</code> deve pertencer a um trecho ligado a esta chave (Pre 1)
	 * @pre <code>composicao</code> nao pode ter sido adicionada antes (Pre 2)
	 * @pre <code>composicao</code> nao pode estar a caminho de outra chave (Pre 3)
	 * 
	 * @post <code>composicao</code> a adicionada ao mapa de composicoes a caminho 
	 * tendo como elemento mapeado um inteiro positivo representando o instante em que 
	 * chegarah na chave (Pos 1)
	 */
	public void adicionarComposicaoACaminho(CComposicao composicao) {
		
		/*Pre 1*/
		assert(
				trechosA.contains(composicao.getTrechoChaveAtual().getId()) || 
				trechosB.contains(composicao.getTrechoChaveAtual().getId())
			);
		
		/*Pre 2*/
		assert(!trensACaminho.containsKey(composicao));
		
		/*Pre 3*/
		boolean achou = false;
		HashMap<String, CChave> mapaChaves = CControle.getControle().getMalha().getChaves();
		Set<String> keys = mapaChaves.keySet();
		
		for (String key:keys)
		{
			CChave chave = (CChave) mapaChaves.get(key);
			if(chave.getTrensACaminho().containsKey(composicao))
			{
				achou = true;
			}
		}
		assert(!achou);
		
		/*<IMPLEMENTACAO>*/
		
		//Resolvendo a equacao a.t²/2 + v0.t + (s0-s) = 0
		float s0 = composicao.getPosicaoAtual();
		float v0 = composicao.getVelAtual();
		float a = composicao.getAcelAtual();
		float s = composicao.getTrechoChaveAtual().getDistancia();
		
		if (composicao.isMovendoAParaB() == false) {
			s = 0f;
			a = -a;
			v0 = -v0;
		}
		
		float tempo = 0f;
		
		if (a != 0) {
			float r[] = CControle.bhaskara(a/2, v0, s0-s);
			System.out.printf("R1:%f\nR2:%f", r[0], r[1]);
			tempo = Math.min(Math.abs(r[0]), Math.abs(r[1]));
		} else {
			tempo = (s-s0)/v0;
		}
		
		
		trensACaminho.put(composicao, (int) Math.floor(tempo));
		
		/*</IMPLEMENTACAO>*/
		
		/*Post 1*/
		assert(trensACaminho.containsKey(composicao));
		assert(trensACaminho.get(composicao) > 0);
		
	}
	
	
	/**
	 * Remove a composicao do mapa de composicoes a caminho desta chave
	 * 
	 * @param composicao uma composicao que se encontra a caminho desta chave
	 * 
	 * @pre <code>composicao</code> deve pertencer ao conjunto de composicoes a 
	 * caminho desta chave
	 * @pre <code>composicao</code> deve ser a proxima composicao a chegar na chave
	 * 
	 * @post <code>composicao</code> nao deve mais estar no conjunto de CComposicoes a caminho
	 * desta chave (Post 1)
	 */
	public void removerComposicaoACaminho(CComposicao composicao) {
		
		/*Pre 1*/
		assert(trensACaminho.containsKey(composicao));
		
		/*Pre 2*/		
		assert(proximaComposicaoACaminho() == composicao);
		
		/*<IMPLEMENTACAO>*/
		trensACaminho.remove(composicao);
		/*</IMPLEMENTACAO>
		
		/*Post 1*/
		assert(!trensACaminho.containsKey(composicao));
		
	}
	
	
	/**
	 * Proxima CComposicao a chegar nesta chave
	 * 
	 * @return A proxima CComposicao que chegara nesta chave
	 * dentre todas as que estao a caminho, null se estiver vazio
	 */
	public CComposicao proximaComposicaoACaminho() {
		
		CComposicao proxima_a_chegar = null;
		int menor_tempo = Integer.MAX_VALUE;
		Set<CComposicao> composicoes = trensACaminho.keySet();
		for(CComposicao atual:composicoes)
		{
			Integer temp = trensACaminho.get(atual);
			int tempoAtual = temp.intValue();
			if(tempoAtual< menor_tempo)
			{
				menor_tempo = tempoAtual;
				proxima_a_chegar = atual;
			}
		}
		
		return proxima_a_chegar;
	}
	
	/**
	 * Remove a composicao que esta passando por essa chave
	 * 
	 * @pre Deve haver uma composicao passando por essa chave (Pre 1)
	 * @post a composicao que estava passando por essa chave 
	 * deve agora se encontrar em um dos trechos ligados a ela (Post 1)
	 * @post Nao deve haver uma composicao passando por essa chave (Post 2)
	 */
	public void removeComposicao()
	{
		/*Pre 1*/
		assert(composicaoPassando != null);
		
		/*<IMPLEMENTACAO>*/
		composicaoPassando.setTrechoChaveAtual(null);
		composicaoPassando = null;
		/*</IMPLEMENTACAO>*/
		
		assert(composicaoPassando == null);
	}
}
